package com.hsyt.pms.security;


import com.alibaba.fastjson.JSON;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;

/**
 * @ClassName: YtAbstractUserDetailsAuthenticationProvider
 * @Description: 用户抽象认证器
 * @author 王勇琳
 * @date 2017年6月29日 下午5:22:37
 */
public abstract class YtAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
	
	protected final Log logger = LogFactory.getLog(getClass());

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private UserCache userCache = new NullUserCache();
	private boolean forcePrincipalAsString = false;
	/** 是否隐藏UsernameNotFoundException */
	protected boolean hideUserNotFoundExceptions = true;
	private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
	private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

	/**
	 * 可扩展认证检查方法
	 * @author 王勇琳
	 * @date 2017年7月5日 上午11:19:49
	 * @param userDetails
	 * @param authentication
	 * @throws AuthenticationException
	 */
	protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;

	public final void afterPropertiesSet() throws Exception {
		Assert.notNull(this.userCache, "UserCache必须注入或已存在");
		Assert.notNull(this.messages, "MessageSourceAccessor必须注入或已存在");
		doAfterPropertiesSet();
	}

	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				messages.getMessage("YtAbstractUserDetailsAuthenticationProvider.onlySupports", "仅支持UsernamePasswordAuthenticationToken"));
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				// 建立用户索引
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			} catch (UsernameNotFoundException notFound) {
				logger.debug("用户'" + username + "'不存在");
				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"YtAbstractUserDetailsAuthenticationProvider.badCredentials", "用户名或密码错误"));
				} else {
					throw notFound;
				}
			}
			Assert.notNull(user, "retrieveUser返回用户为空 - retrieveUser方法违反了接口约束");
		}
		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		} catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// 如果缓存校验异常，需要重新数据库校验
				cacheWasUsed = false;
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
			} else {
				throw exception;
			}
		}
		postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()));
		//TODO UserDetails的"loginType","terminalType","appAuthName","userType"这四个参数可以在此处封装一下
		result.setDetails(authentication.getDetails());
		return result;
	}

	protected void doAfterPropertiesSet() throws Exception {
	}

	public UserCache getUserCache() {
		return userCache;
	}

	public boolean isForcePrincipalAsString() {
		return forcePrincipalAsString;
	}

	public boolean isHideUserNotFoundExceptions() {
		return hideUserNotFoundExceptions;
	}

	/**
	 * 根据用户名查找用户
	 * @author 王勇琳
	 * @date 2017年7月5日 上午11:20:05
	 * @param username
	 * @param authentication
	 * @return
	 * @throws AuthenticationException
	 */
	protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;

	public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
		this.forcePrincipalAsString = forcePrincipalAsString;
	}

	public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
		this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public void setUserCache(UserCache userCache) {
		this.userCache = userCache;
	}

	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}

	protected UserDetailsChecker getPreAuthenticationChecks() {
		return preAuthenticationChecks;
	}

	public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
		this.preAuthenticationChecks = preAuthenticationChecks;
	}

	protected UserDetailsChecker getPostAuthenticationChecks() {
		return postAuthenticationChecks;
	}

	public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
		this.postAuthenticationChecks = postAuthenticationChecks;
	}

	public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
		this.authoritiesMapper = authoritiesMapper;
	}

	private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
		public void check(UserDetails user) {
			logger.debug("用户账户信息："+JSON.toJSON(user));
			if (!user.isAccountNonLocked()) {
				logger.debug("用户账户被锁定");
				throw new LockedException(messages.getMessage(
						"YtAbstractUserDetailsAuthenticationProvider.locked", "用户账户被锁定"));
			}
			if (!user.isEnabled()) {
				logger.debug("用户账户已注销或停用");
				throw new DisabledException(messages.getMessage(
						"YtAbstractUserDetailsAuthenticationProvider.disabled", "账户已注销或停用"));
			}
			if (!user.isAccountNonExpired()) {
				logger.debug("用户账户已过期");
				throw new AccountExpiredException(messages.getMessage(
						"YtAbstractUserDetailsAuthenticationProvider.expired", "用户账户已过期"));
			}
		}
	}

	private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
		public void check(UserDetails user) {
			if (!user.isCredentialsNonExpired()) {
				logger.debug("用户账户密令已过期");
				throw new CredentialsExpiredException(messages.getMessage(
						"YtAbstractUserDetailsAuthenticationProvider.credentialsExpired", "用户账户密令已过期"));
			}
		}
	}
	
}
